-
Notifications
You must be signed in to change notification settings - Fork 887
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Core part for local nft pinning #16073
Conversation
af506e2
to
fa5f781
Compare
|
ba27385
to
3886be5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some early feedback on the first commit, haven't checked the change in IpfsService.
components/ipfs/ipfs_json_parser.h
Outdated
static bool GetAddPinsResultFromJSON(const std::string& json, | ||
ipfs::AddPinResult* add_pin_result); | ||
static bool GetGetPinsResultFromJSON(const std::string& json, | ||
ipfs::GetPinsResult* result); | ||
static bool GetRemovePinsResultFromJSON( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think returning absl::optionalipfs::*PinResult is preferred, let's start with these new functions.
components/ipfs/ipfs_json_parser.cc
Outdated
return false; | ||
} | ||
|
||
const base::Value* pins_arr = records_v->FindKey("Pins"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const base::Dict* dict = records_v->GetIfDict();
Then we can use dict->FindList and dict->FindInt below.
By using dict->FindList, we don't need to check type explicitly as FindList will return nullptr if it's not list type.
components/ipfs/ipfs_json_parser.cc
Outdated
return false; | ||
} | ||
|
||
auto progress = records_v->FindIntKey("Progress"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this one is deprecated and should use Value::Dict::FindList() instead.
components/ipfs/ipfs_json_parser.cc
Outdated
return false; | ||
} | ||
|
||
const base::Value* pins_arr = records_v->FindKey("Pins"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DITTO
components/ipfs/ipfs_json_parser.cc
Outdated
return false; | ||
} | ||
|
||
const base::Value* keys = records_v->FindKey("Keys"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const base::Dict* dict = records_v->GetIfDict();
then use dict->FindDict directly.
components/ipfs/ipfs_json_parser.cc
Outdated
VLOG(1) << "Missing Type for " << it.first; | ||
return false; | ||
} | ||
const std::string* type = it.second.FindStringKey("Type"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FindStringKey is deprecated.
IpfsBasePinService::IpfsBasePinService() {} | ||
|
||
IpfsBasePinService::~IpfsBasePinService() {} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
= default for both
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cypt4 Just a friendly reminder, ^ is not resolved yet.
private: | ||
void OnAddPinResult(bool status, absl::optional<AddPinResult> result); | ||
|
||
PrefService* prefs_service_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
raw_ptr for pointer fields in this file
~AddPinResult(); | ||
AddPinResult(const AddPinResult&); | ||
std::vector<std::string> pins; | ||
int progress; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
int progress = 0;
base::Value::Dict& update_dict = update->GetDict(); | ||
|
||
for (const auto& cid : cids_) { | ||
base::Value::List* list = update_dict.FindList(cid); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EnsureList
absl::optional<AddPinResult> result) { | ||
if (status && result) { | ||
for (const auto& cid : cids_) { | ||
if (std::find(result->pins.begin(), result->pins.end(), cid) == |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
base::Contains
components/ipfs/ipfs_service.cc
Outdated
|
||
bool success = response.Is2XXResponseCode(); | ||
|
||
if (!success) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just if (!response.Is2XXResponseCode())
same for methods below
components/ipfs/ipfs_service.cc
Outdated
for (const auto& cid : cids) { | ||
gurl = net::AppendQueryParameter(gurl, kArgQueryParam, cid); | ||
} | ||
LOG(ERROR) << "XXZZZ " << gurl.spec(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Y is missing
components/ipfs/ipfs_service.cc
Outdated
"POST", gurl, std::string(), std::string(), false, | ||
base::BindOnce(&IpfsService::OnPinRemoveResult, base::Unretained(this), | ||
iter, std::move(callback)), | ||
{{net::HttpRequestHeaders::kOrigin, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{{net::HttpRequestHeaders::kOrigin,
url::Origin::Create(gurl).Serialize()}}
worth an anonymous function to stop copypasting.
components/ipfs/ipfs_service.cc
Outdated
|
||
ipfs::GetPinsResult result; | ||
bool parse_result = | ||
IPFSJSONParser::GetGetPinsResultFromJSON(response.body(), &result); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use response.value_body() (returns base::Value) after rebasing. #15933 just merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same for methods below
components/ipfs/ipfs_service.h
Outdated
using AddPinCallback = | ||
base::OnceCallback<void(bool, absl::optional<AddPinResult>)>; | ||
using RemovePinCallback = | ||
base::OnceCallback<void(bool, absl::optional<RemovePinResult>)>; | ||
using GetPinsCallback = | ||
base::OnceCallback<void(bool, absl::optional<GetPinsResult>)>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For these three callbacks let's remove bool argument and consider empty optional as failure
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( | ||
FROM_HERE, | ||
base::BindOnce(&IpfsLocalPinService::AddGcTask, base::Unretained(this)), | ||
base::Minutes(1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there is no guarantee that after a minute delay from start IpfsLocalPinService is always alive. We might get browser shutdown crash here.
ipfs_base_pin_service_->AddJob(std::make_unique<AddLocalPinJob>( | ||
prefs_service_, ipfs_service_, prefix, cids, | ||
base::BindOnce(&IpfsLocalPinService::OnAddJobFinished, | ||
base::Unretained(this), std::move(callback)))); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is quite hard to track how lifetimes of callback and this correlate. Better pass it by weak ptr and be safe.
} | ||
|
||
void IpfsBasePinService::OnDaemonStarted() { | ||
ipfs_service_->GetConnectedPeers(base::NullCallback(), 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why NullCallback? What is the purpose of GetConnectedPeers call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To ensure that daemon is fully loaded
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment then.
PrefService* prefs_; | ||
|
||
// JsonRpcService is used to fetch token metadata | ||
JsonRpcService* json_rpc_service_; | ||
ipfs::IpfsLocalPinService* local_pin_service_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
raw_ptr
|
||
void BraveWalletAutoPinService::OnTokenRemoved(BlockchainTokenPtr token) { | ||
const auto iter = | ||
std::remove_if(queue_.begin(), queue_.end(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
base::EraseIf from cxx20_erase_deque.h
IntentData::IntentData(const BlockchainTokenPtr& token, | ||
Operation operation, | ||
absl::optional<std::string> service) | ||
: token(token.Clone()), operation(operation), service(service) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
service(std::move(service))
|
||
void BraveWalletAutoPinService::Restore() { | ||
brave_wallet_service_->GetUserAssets( | ||
mojom::kMainnetChainId, mojom::CoinType::ETH, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it intentional to get assets for mainnet always?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added new GetAllUserAssets
|
||
namespace brave_wallet { | ||
|
||
enum Operation { ADD = 0, DELETE = 1, VALIDATE = 2 }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per style guide please use kAdd, kDelete and kValidate
if (!result) { | ||
AddOrExecute(std::make_unique<IntentData>(current_->token, Operation::ADD, | ||
current_->service)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should that be if (result)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We re-add token if it is corrupted here
virtual mojom::TokenPinStatusPtr GetTokenStatus( | ||
absl::optional<std::string> service, | ||
const mojom::BlockchainTokenPtr& token); | ||
virtual absl::optional<base::Time> GetLastValidateTime( | ||
absl::optional<std::string> service, | ||
const mojom::BlockchainTokenPtr& token); | ||
virtual std::set<std::string> GetTokens( | ||
const absl::optional<std::string>& service); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const absl::optionalstd::string& service
if (!service) { | ||
local_pin_service_->ValidatePins( | ||
GetPath(absl::nullopt, token), cids.value(), | ||
base::BindOnce(&BraveWalletPinService::OnTokenValidated, | ||
base::Unretained(this), service, std::move(callback), | ||
std::move(token))); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this ok we don't do anything if there as service
string? callback would not be resolved then.
if (!service) { | ||
local_pin_service_->AddPins( | ||
GetPath(service, token), cids, | ||
base::BindOnce(&BraveWalletPinService::OnTokenPinned, | ||
base::Unretained(this), absl::nullopt, | ||
std::move(callback), std::move(token))); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also losing callback if there is a service
.
cids.push_back(ExtractCID(token_url).value()); | ||
auto* image = parsed_result->FindStringKey("image"); | ||
if (image) { | ||
cids.push_back(ExtractCID(*image).value()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it safe to call .value() here? It is unclear where validation happens.
return base::StrCat({kNftPart, ".", service.value_or(kLocalService), ".", | ||
base::NumberToString(static_cast<int>(token->coin)), ".", | ||
token->chain_id, ".", token->contract_address, ".", | ||
token->token_id}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(same for BraveWalletPinService::GetTokens)
Is it safe to make pref path this way? What if any part of it has a dot?
Do we really need keep such hierarchy in prefs? Maybe just an array of {service, coin, chain_id, etc...} ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is needed some times to resolve tokens by group like service where they are pinned.
I've added protection in GetPath method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cypt4 Note that token_id might be an empty string for other NFTs that's not ERC721, do we need to have this detailed hierarchy? Are all service, coin, chain_id, contract_address, token_id needs to be in the key path or we only need a few of them like service for grouping?
return mojom::TokenPinStatusCode::STATUS_PINNED; | ||
} else if (status == "pinning_in_progress") { | ||
return mojom::TokenPinStatusCode::STATUS_PINNING_IN_PROGRESS; | ||
} else if (status == "unpining_in_progress") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everywhere in the file pining
->pinning
DictionaryPrefUpdate update(prefs_service_, kIPFSPinnedCids); | ||
base::Value::Dict& update_dict = update->GetDict(); | ||
|
||
bool verification_pased = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
passed
8fdd79a
to
077c9eb
Compare
app/brave_settings_strings.grdp
Outdated
<message name="IDS_SETTINGS_IPFS_NFT_AUTO_PINNING_ENABLED_LABEL" desc="The text label for NFT auto-pinning to IPFS feature."> | ||
Automatically pin NFTs to the local IPFS node | ||
</message> | ||
<message name="IDS_SETTINGS_IPFS_NFT_AUTO_PINNING_ENABLED_DESC" desc="The description for IPFS companion switch in settings"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Content of desc seems wrong here?
browser/resources/settings/brave_ipfs_page/brave_ipfs_page.html
Outdated
Show resolved
Hide resolved
return base::StrCat({kNftPart, ".", service.value_or(kLocalService), ".", | ||
base::NumberToString(static_cast<int>(token->coin)), ".", | ||
token->chain_id, ".", token->contract_address, ".", | ||
token->token_id}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cypt4 Note that token_id might be an empty string for other NFTs that's not ERC721, do we need to have this detailed hierarchy? Are all service, coin, chain_id, contract_address, token_id needs to be in the key path or we only need a few of them like service for grouping?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to clarify some IPFS basics. When pinning files that are already present on IPFS and has a CID we don't actually fetch them in the browser process, but it happens in the Kubo daemon? Should we check that the files we pin are images and not something else? If we do so can you point me to where in the code it happens?
const std::string& path) { | ||
std::vector<std::string> parts = | ||
base::SplitString(path, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); | ||
if (parts.size() != 6) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we store data like this instead of using something more common (e.g. JSON)? Is it specific to chromium?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes we store a lot of things in the PrefsService, like saved accounts, assets, NFTs. Using raw JSON means we should have some sort of transactions for write operations. PrefsService already has such mechanism.
weak_ptr_factory_.GetWeakPtr(), service, | ||
std::move(callback), std::move(token))); | ||
} else { | ||
// Remote pinning not implemented yet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we locally pin files that are already remotely pinned (by Pinata for example)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, NFTs from OpenSea or other NFT platforms are most likely are pinned in some way. But they could be unpinned in any moment.
if (image) { | ||
auto image_cid = ExtractCID(*image); | ||
if (image_cid) { | ||
cids.push_back(image_cid.value()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we check if the file we pin is an image?
#if BUILDFLAG(ENABLE_IPFS) | ||
brave_wallet::BraveWalletAutoPinServiceFactory::GetServiceForContext(profile); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these being called here in the first place? You should be using BrowserContextKeyedServiceFactory:: ServiceIsCreatedWithContext
@@ -44,6 +44,7 @@ | |||
#endif | |||
|
|||
#if BUILDFLAG(ENABLE_IPFS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is a brave wallet service guarded by enabled_ipfs, they are not guarded by enable_ipfs in the BUILD.gn
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
header buildflags and gn buildflags should always match regardless of whether the code is only use when some other buildflag is enabled
Resolves brave/brave-browser#19283
Submitter Checklist:
QA/Yes
orQA/No
;release-notes/include
orrelease-notes/exclude
;OS/...
) to the associated issuenpm run test -- brave_browser_tests
,npm run test -- brave_unit_tests
,npm run lint
,npm run gn_check
,npm run tslint
git rebase master
(if needed)Reviewer Checklist:
gn
After-merge Checklist:
changes has landed on
Test Plan:
This is core part, so there is actually nothing to be tested